_Pragma("once")

#include "pch.hpp"

namespace sdl_engine {

using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

template <typename T>
struct Vector {
    union {
        T x;
        T w;
    };
    union {
        T y;
        T h;
    };

    Vector(): x{}, y{} {}
    Vector(T x, T y): x(x), y(y) {}
    Vector(const Vector& o): x(o.x), y(o.y) {}

    template <typename T2>
    Vector<T>(const Vector<T2>& o): x(o.x), y(o.y) {}

    template <typename T2>
    inline bool operator==(const Vector<T2>& o) const {
        return this->x == o.x && this->y == o.y;
    }

    inline bool operator==(const Vector& o) const {
        if constexpr (std::is_floating_point_v<T>) {
            return abs(x - o.x) <= std::numeric_limits<T>::epsilon() &&
                   abs(y - o.y) <= std::numeric_limits<T>::epsilon();
        } else {
            return x == o.x && y == o.y;
        }
    }

    template <typename T2>
    inline bool operator!=(const Vector<T2>& o) const {
        return !(*this == o);
    }

    inline bool operator!=(const Vector& o) const {
        return !(*this == o);
    }

    inline Vector operator+(const Vector& o) const {
        Vector v;
        v.x = x + o.x;
        v.y = y + o.y;
        return v;
    }

    inline Vector operator-(const Vector& o) const {
        Vector v;
        v.x = x - o.x;
        v.y = y - o.y;
        return v;
    }

    inline Vector& operator+=(const Vector& o) {
        *this = *this + o;
        return *this;
    }

    inline Vector& operator-=(const Vector& o) {
        *this = *this - o;
        return *this;
    }

    inline Vector& operator=(const Vector& o) {
        x = o.x;
        y = o.y;
        return *this;
    }

    template <typename T2>
    inline Vector<T>& operator=(const Vector<T2>& o) {
        this->x = o->x;
        this->y = o->y;
        return *this;
    }

    template <typename MulT>
    inline Vector operator*(MulT value) const {
        Vector<T> v = {this->x * value, this->y * value};
        return v;
    }

    template <typename DivT>
    inline Vector operator/(DivT value) const {
        Vector<T> v = {this->x / value, this->y / value};
        return v;
    }

    template <typename MulT>
    inline Vector& operator*=(MulT value) {
        this->x *= value;
        this->y *- value;
        return *this;
    }

    template <typename DivT>
    inline Vector& operator/=(DivT value) {
        this->x /= value;
        this->y /= value;
        return *this;
    }

};

template <typename T, typename MulT>
inline Vector<T> operator*(MulT value, const Vector<T>& v) {
    Vector<T> ret = {v.x * value, v.y * value};
    return ret;
}

template <typename T, typename DivT>
inline Vector<T> operator/(DivT value, const Vector<T>& v) {
    Vector<T> ret = {v.x / value, v.y / value};
    return ret;
}

template <typename T>
inline std::ostream& operator<<(std::ostream& o, const Vector<T>& v) {
    o << "vec(" << v.x << "," << v.y << ")";
    return o;
}

template <typename T>
inline Vector<T> vector(const T& a, const T& b) { return {a, b}; }

using Point = Vector<int>;
using FPoint = Vector<float>;
using Size = Vector<int>;
using Vec = Vector<float>;

// TODO replace Rect by template
struct Rect {
    Point position;
    Size size;

    Rect() = default;
    Rect(const Point& p, const Size& s): position(p), size(s) {}
    Rect(int x, int y, int w, int h): position(x, y), size(w, h) {}
    Rect(const Rect& r): position(r.position), size(r.size) {}

    inline bool Valid() const { return size.w > 0 && size.h > 0; }

    inline Rect& operator=(const Rect& o) {
        position = o.position;
        size = o.size;
        return *this;
    }

    inline bool operator==(const Rect& o) const {
        return o.position == position && o.size == size;
    }
};

template <typename T>
inline std::ostream& operator<<(std::ostream& o, const Rect& r) {
    o << "rect(" << r.position << "," << r.size << ")";
    return o;
}

inline Point point(int a, int b) { return {a, b}; }
inline Size size(int a, int b) { return {a, b}; }
inline Vec vec(float a, float b) { return {a, b}; }
inline Rect rect(int x1, int y1, int w, int h) { return {point(x1, y1), size(w, h)}; }
inline Rect rect(const Point& p1, const Point& p2) {
    return {
            point(std::fmin(p1.x, p2.x), std::fmin(p1.y, p2.y)),
            size(std::abs(p1.x - p2.x), std::abs(p1.y - p2.y))
           };
}

struct Color {
    u8 r;
    u8 g;
    u8 b;
    u8 a = 255;

    static const Color Red;
    static const Color Green;
    static const Color Blue;
    static const Color Yello;
    static const Color White;
    static const Color Black;
    static const Color Gray;
};

inline Color color(u8 r, u8 g, u8 b, u8 a = 255) { return {r, g, b, a}; }

inline bool PointInRect(const Point& p, const Rect& r) {
    return (p.x >= r.position.x && p.x <= r.position.x + r.size.w) &&
           (p.y >= r.position.y && p.y <= r.position.y + r.size.h);
}

inline bool IsRectsIntersect(const Rect& r1, const Rect& r2) {
    return !(r1.position.x >= r2.position.x + r2.size.w ||
             r1.position.x + r1.size.w <= r2.position.x ||
             r1.position.y >= r2.position.y + r2.size.h ||
             r1.position.y + r1.size.h <= r2.position.y);
}

inline Rect IntersectRects(const Rect& r1, const Rect& r2) {
    if (IsRectsIntersect(r1, r1)) {
        auto x1 = std::max(r1.position.x, r2.position.x),
             y1 = std::max(r1.position.y, r2.position.y),
             x2 = std::min(r1.position.x + r1.size.w,
                           r2.position.x + r2.size.w),
             y2 = std::min(r1.position.y + r1.size.h,
                           r2.position.y + r2.size.h);
        return rect(x1, y1, x2 - x1, y2 - y1);
    } else {
        return rect(-1, -1, -1, -1);
    }
}

}
